/*
WLVGoogleImageSelector - Google Picasa Web Plugin for Windows Live Writer
Copyright (C) 2007 Timothy Parez

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

using System;
using System.Data;
using System.Xml;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Net;

namespace Arendee.WindowsLiveWriter.Plugins
{
    public struct ExifDataStruct
    {
        public double FStop;
        public double Distance;
        public double Exposure;
        public bool Flash;
        public double FocalLength;
        public int Iso;
        public string UniqueId;
        public string Make;
        public string Model;
        public string TimeStamp;
    }

    public struct GPhotoStruct
    {
        public string Id;
        public string Version;
        public string AlbumId;
        public int Width;
        public int Height;
        public int Size;
        public string Checksum;
        public string TimeStamp;
        public bool CommentingEnabled;
        public int CommentCount;
        
    }

    public struct GoogleAlbum
    {
        public string Name;
        public string Title;

        public override string ToString()
        {
            return this.Title;
        }
    }


    public class GoogleImage
    {
        string id;
        string title;
        string summary;
        string url;
        System.Drawing.Image thumbnail;
        ExifDataStruct exifData;
        GPhotoStruct gPhotoData;
        
        public GoogleImage()
        {
           
        }

        public string Id
        {
            get
            { 
                return this.id;
            }
            set
            {
                this.id = value;
            }
        }

        public string Title
        {
            get
            { 
                return this.title;
            }
            set
            {
                this.title = value;
            }
        }

        public string Summary
        {
            get
            { 
                return this.summary;
            }
            set
            {
                this.summary = value;
            }
        }

        public System.Drawing.Image Thumbnail
        {
            get
            {
                return this.thumbnail;
            }
            set
            {
                this.thumbnail = value;
            }
        }
        
        public string Url
        {
            get
            { 
                return this.url;
            }
            set
            {
                this.url = value;
            }
        }

        public ExifDataStruct ExifData
        {
            get
            {
                return this.exifData;
            }
            set
            {
                this.exifData = value;
            }
        }

        public GPhotoStruct GPhotoData
        {
            get
            {
                return this.gPhotoData;
            }
            set
            {
                this.gPhotoData = value;
            }
        }

        public static List<GoogleAlbum> GetAlbums(string GoogleId)
        {
            //Create the feed url
            string albumFeedUrl = String.Format("http://picasaweb.google.com/data/feed/api/user/{0}?kind=album", GoogleId);

            //Load a new XmlDocument
            XmlDocument albumFeed = new XmlDocument();

            try
            {
                albumFeed.Load(albumFeedUrl);
            }
            catch
            {
                return null;
            }

            //Get the namespace manager
            XmlNamespaceManager nsMgr = GoogleImage.GetGoogleNamespaceManager(albumFeed);

            //Create a new list
            List<GoogleAlbum> albums = new List<GoogleAlbum>();

            XmlNodeList entryNodes = albumFeed.GetElementsByTagName("entry");
            foreach (XmlNode entryNode in entryNodes)
            {
                GoogleAlbum album = new GoogleAlbum();
                album.Name = entryNode.SelectSingleNode("./gphoto:name", nsMgr).InnerText;
                album.Title = entryNode.SelectSingleNode("./def:title", nsMgr).InnerText;
                albums.Add(album);
            }

            return albums;
        }

        public static List<GoogleImage> Get(string GoogleId, string Album)
        {
            List<GoogleImage> images = new List<GoogleImage>();

            //Create the feed url
            string albumQuery = ((Album != null && Album.Length > 0) ? String.Format("/album/{0}", Album) : "");
            string imageFeedUrl = String.Format(@"http://picasaweb.google.com/data/feed/api/user/{0}{1}?kind=photo", GoogleId, albumQuery);

            //Todo: when using .NET 3.5, use LINQ for XML instead
            XmlDocument imageFeed = new XmlDocument();

            try
            {
                imageFeed.Load(imageFeedUrl);
            }
            catch
            {
                //Return the empty list
                return images;
            }

            //Create a namespace manager
            XmlNamespaceManager nsMgr = GoogleImage.GetGoogleNamespaceManager(imageFeed);

            XmlNodeList entryNodes = imageFeed.GetElementsByTagName("entry");
            foreach (XmlNode entryNode in entryNodes)
            {
                //Get the various nodes
                XmlNode idNode = entryNode.SelectSingleNode("./def:id", nsMgr);
                XmlNode titleNode = entryNode.SelectSingleNode("./def:title", nsMgr);
                XmlNode summaryNode = entryNode.SelectSingleNode("./def:summary", nsMgr);
                XmlNode urlNode = entryNode.SelectSingleNode("./media:group/media:content", nsMgr);
                XmlNode exifFStopNode = entryNode.SelectSingleNode("./exif:tags/exif:fstop", nsMgr);
                XmlNode exifDistanceNode = entryNode.SelectSingleNode("./exif:tags/exif:distance", nsMgr);
                XmlNode exifExporeNode = entryNode.SelectSingleNode("./exif:tags/exif:expore", nsMgr);
                XmlNode exifFlashNode = entryNode.SelectSingleNode("./exif:tags/exif:flash", nsMgr);
                XmlNode exifFocalLengthNode = entryNode.SelectSingleNode("./exif:tags/exif:focallength", nsMgr);
                XmlNode exifMakeNode = entryNode.SelectSingleNode("./exif:tags/exif:make", nsMgr);
                XmlNode exifModelNode = entryNode.SelectSingleNode("./exif:tags/exif:model", nsMgr);
                XmlNode exifIsoNode = entryNode.SelectSingleNode("./exif:tags/exif:iso", nsMgr);
                XmlNode exifTimeNode = entryNode.SelectSingleNode("./exif:tags/exif:time", nsMgr);
                XmlNode exifUniqueIdNode = entryNode.SelectSingleNode("./exif:tags/exif:imageUniqueId", nsMgr);
                XmlNode gphotoIdNode = entryNode.SelectSingleNode("./gphoto:id",nsMgr);
                XmlNode gphotoVersionNode = entryNode.SelectSingleNode("./gphoto:version",nsMgr);
                XmlNode gphotoWidthNode = entryNode.SelectSingleNode("./gphoto:width",nsMgr);
                XmlNode gphotoHeightNode = entryNode.SelectSingleNode("./gphoto:height",nsMgr);
                XmlNode gphotoSizeNode = entryNode.SelectSingleNode("./gphoto:size",nsMgr);
                XmlNode gphotoChecksumNode = entryNode.SelectSingleNode("./gphoto:checksum",nsMgr);
                XmlNode gphotoTimeStampNode = entryNode.SelectSingleNode("./gphoto:timestamp",nsMgr);

                GoogleImage image = new GoogleImage();

                image.id = idNode.InnerText;
                image.title = titleNode.InnerText;
                image.summary = summaryNode.InnerText;
                image.url = urlNode.Attributes["url"].Value;

                //We have to check for null values, as not all these values are present for every image
                image.exifData.FStop = (exifFStopNode != null ? Convert.ToDouble(exifFStopNode.InnerText) : 0);
                image.exifData.Distance = (exifDistanceNode != null ? Convert.ToDouble(exifDistanceNode.InnerText) : 0);
                image.exifData.Exposure = (exifExporeNode != null ? Convert.ToDouble(exifExporeNode.InnerText) : 0);
                image.exifData.Flash = (exifFlashNode != null ? Convert.ToBoolean(exifFlashNode.InnerText) : false);
                image.exifData.FocalLength = (exifFocalLengthNode != null ? Convert.ToDouble(exifFocalLengthNode.InnerText) : 0);
                image.exifData.Make = (exifMakeNode != null ? exifMakeNode.InnerText : "");
                image.exifData.Model = (exifModelNode != null ? exifModelNode.InnerText : "");
                image.exifData.Iso = (exifIsoNode != null ? Convert.ToInt32(exifIsoNode.InnerText) : 0);
                image.exifData.TimeStamp = (exifTimeNode != null ? exifTimeNode.InnerText : "");
                image.exifData.UniqueId = (exifUniqueIdNode != null ?  exifUniqueIdNode.InnerText : "");
                image.gPhotoData.Id = (gphotoIdNode != null ? gphotoIdNode.InnerText : "");
                image.gPhotoData.Version = (gphotoVersionNode != null ? gphotoVersionNode.InnerText : "");
                image.gPhotoData.Width = (gphotoWidthNode != null ? Convert.ToInt32(gphotoWidthNode.InnerText) : 0);
                image.gPhotoData.Height = (gphotoHeightNode != null ? Convert.ToInt32(gphotoHeightNode.InnerText) : 0);
                image.gPhotoData.Size = (gphotoSizeNode != null ? Convert.ToInt32(gphotoSizeNode.InnerText) : 0);
                image.gPhotoData.Checksum = (gphotoChecksumNode != null ? gphotoChecksumNode.InnerText : "");
                image.gPhotoData.TimeStamp = (gphotoTimeStampNode != null ? gphotoTimeStampNode.InnerText : "");
               
                images.Add(image);
            }

            GoogleImage.cacheThumbnails(ref images);            

            return images;
        }

        private static void cacheThumbnails(ref List<GoogleImage> Images)
        {
            SQLiteConnection dbConnection = DatabaseManager.GetConnection();
            
            //Create the queries
            SQLiteCommand selectCommand = new SQLiteCommand("SELECT Thumbnail FROM googleimage WHERE Id = @Id", dbConnection);
            selectCommand.Parameters.Add("@Id", DbType.String);

            SQLiteCommand insertCommand = new SQLiteCommand("INSERT INTO googleimage (Id, Thumbnail, LastUpdate) VALUES (@Id, @Thumbnail, @LastUpdate)", dbConnection);
            insertCommand.Parameters.Add("@Id", DbType.String);
            insertCommand.Parameters.Add("@Thumbnail", DbType.Object);
            insertCommand.Parameters.Add("@LastUpdate", DbType.DateTime).Value = System.DateTime.Now;

            //Open the connection
            dbConnection.Open();

            foreach (GoogleImage image in Images)
            {
                selectCommand.Parameters["@Id"].Value = image.Id;
                object result = selectCommand.ExecuteScalar();
                if (result != null)
                {
                    byte[] buffer = (byte[])result;
                    MemoryStream stream = new MemoryStream(buffer);
                    Image thumb = Image.FromStream(stream);
                    stream.Close();

                    image.thumbnail = thumb;
                }
                else
                {
                    //Get a thumbnail (Let Google do most of the work by getting size 72)
                    image.Thumbnail = image.CreateThumbnail();
                    MemoryStream stream = new MemoryStream();
                    image.thumbnail.Save(stream, ImageFormat.Jpeg);
                    byte[] buffer = stream.ToArray();
                    stream.Close();

                    insertCommand.Parameters["@Id"].Value = image.Id;
                    insertCommand.Parameters["@Thumbnail"].Value = buffer;
                    insertCommand.ExecuteNonQuery();
                }
            }

            dbConnection.Close();                          
        }

        private static MemoryStream GetMemoryStreamFromImageUrl(string ImageUrl)
        {
            HttpWebRequest request = null;
            HttpWebResponse response = null;
            Stream responseStream = null;

            try
            {
                request = (HttpWebRequest)WebRequest.Create(ImageUrl);
                response = (HttpWebResponse)request.GetResponse();
                responseStream = response.GetResponseStream();

                byte[] fileSize = new byte[response.ContentLength];
                int bufferSize = 4096;
                byte[] buffer = new byte[bufferSize];

                int targetBufferIndex = 0;
                while (true)
                {
                    int n = responseStream.Read(buffer, 0, bufferSize);
                    if (n == 0) break;
                    Array.Copy(buffer, 0, fileSize, targetBufferIndex, n);
                    targetBufferIndex += n;
                }

                return new MemoryStream(fileSize, 0, fileSize.Length);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (null != responseStream) responseStream.Close();
                if (null != response) response.Close();
                if (null != request) request.Abort();
            }
        }

        private static XmlNamespaceManager GetGoogleNamespaceManager(XmlDocument DataDocument)
        {
            XmlNamespaceManager nsMgr = new XmlNamespaceManager(DataDocument.NameTable);
            nsMgr.AddNamespace("gml", "http://www.opengis.net/gml");
            nsMgr.AddNamespace("georss", "http://www.georss.org/georss");
            nsMgr.AddNamespace("gphoto", "http://schemas.google.com/photos/2007");
            nsMgr.AddNamespace("openSearch", "http://a9.com/-/spec/opensearchrss/1.0/");
            nsMgr.AddNamespace("geo", "http://www.w3.org/2003/01/geo/wgs84_pos#");
            nsMgr.AddNamespace("photo", "http://www.pheed.com/pheed/");
            nsMgr.AddNamespace("media", "http://search.yahoo.com/mrss/");
            nsMgr.AddNamespace("batch", "http://schemas.google.com/gdata/batch");
            nsMgr.AddNamespace("exif", "http://schemas.google.com/photos/exif/2007");
            nsMgr.AddNamespace("def", "http://www.w3.org/2005/Atom");

            return nsMgr;
        }


        /// <summary>
        /// Creates a Thumbnail of the current image
        /// </summary>
        /// <returns>An instance of System.Drawing.Image containing the Thumbnail</returns>
        private System.Drawing.Image CreateThumbnail()
        {
            string ImageUrl = String.Concat(this.url, "?imgmax=72");

            //Get the original image
            System.Drawing.Image original = System.Drawing.Image.FromStream(GoogleImage.GetMemoryStreamFromImageUrl(ImageUrl));

            //First we need to crop the original image to 1:1
            int targetSize = (original.Height < original.Width ? original.Height : original.Width);

            Bitmap croppedBitmap = new Bitmap(targetSize, targetSize, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            croppedBitmap.SetResolution(72, 72);

            Graphics croppedGraphics = Graphics.FromImage(croppedBitmap);
            croppedGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            croppedGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
            croppedGraphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
            croppedGraphics.DrawImage(original, new Rectangle(0, 0, targetSize, targetSize), new Rectangle(0, 0, targetSize, targetSize), GraphicsUnit.Pixel);

            //Store the cropped bitmap in a memory stream first
            MemoryStream croppedMemoryImage = new MemoryStream();
            croppedBitmap.Save(croppedMemoryImage, ImageFormat.Jpeg);

            //Now create the thumbnail from the cropped image
            System.Drawing.Image croppedImage = System.Drawing.Image.FromStream(croppedMemoryImage);
            System.Drawing.Image thumbnailImage = croppedImage.GetThumbnailImage(48, 48, null, IntPtr.Zero);

            //Clean up
            original.Dispose();
            croppedBitmap.Dispose();
            croppedGraphics.Dispose();
            croppedImage.Dispose();
            croppedMemoryImage.Close();
            croppedMemoryImage.Dispose();

            return thumbnailImage;
        }
    }
}